/*
	i8250.c
	
	http://ascii-table.com/ansi-escape-sequences.php
	http://www.asciitable.com/
	
	Meng Sun(c) leon.meng.sun@gmail.com
	Source code released under LGPLv3
	https://www.gnu.org/licenses/lgpl-3.0.txt
*/

#include "i8250.h"
#include "i8259a.h"

static unsigned char i8250OutBuffer[I8250BUFFERSIZE];

static unsigned char i8250OutBufferProducerPtr = 0;

static unsigned char i8250OutBufferConsumerPtr = 0; /*Next available slot in buffer*/

static unsigned char i8250InpBuffer[I8250BUFFERSIZE];

static unsigned char i8250InpBufferProducerPtr = 0;

static unsigned char i8250InpBufferConsumerPtr = 0; /*Next available slot in buffer*/

static void i8250DumpCharBufToLine(void){
	
	unsigned char sentChar;
	
	if (i8250OutBufferConsumerPtr == i8250OutBufferProducerPtr) return;
	
	if (i8250OutBuffer[i8250OutBufferConsumerPtr] == '\0'){
		i8250OutBufferConsumerPtr = (i8250OutBufferConsumerPtr + 1) % I8250BUFFERSIZE;
		return;
	}
	
	sentChar = i8250OutBuffer[i8250OutBufferConsumerPtr];
	i8250OutBufferConsumerPtr = (i8250OutBufferConsumerPtr + 1) % I8250BUFFERSIZE;
	THR = sentChar;
	
	return;
}

void i8250PutChar(unsigned char ch){
	
	i8250OutBuffer[i8250OutBufferProducerPtr] = ch;
	i8250OutBufferProducerPtr = (i8250OutBufferProducerPtr + 1) % I8250BUFFERSIZE;
	i8250OutBuffer[i8250OutBufferProducerPtr] = '\0';
	i8250OutBufferProducerPtr = (i8250OutBufferProducerPtr + 1) % I8250BUFFERSIZE;
	
	i8250DumpCharBufToLine();
	
	return;
}

void i8250PutStr(unsigned char* buffer){

	unsigned char cptr = 0;
	
	if (buffer == NULL) return;
	
	while (buffer[cptr] != '\0'){
		i8250OutBuffer[i8250OutBufferProducerPtr] = buffer[cptr];
		i8250OutBufferProducerPtr = (i8250OutBufferProducerPtr + 1) % I8250BUFFERSIZE;
		++cptr;
	}
	i8250OutBuffer[i8250OutBufferProducerPtr] = '\0';
	i8250OutBufferProducerPtr = (i8250OutBufferProducerPtr + 1) % I8250BUFFERSIZE;
	
	i8250DumpCharBufToLine();

	return;
}

void i8250SetGraphicsMode(ansiesc_gmode_txt txt, ansiesc_gmode_fgc fgc, ansiesc_gmode_bgc bgc){

	unsigned char pattern[] = {0x1b, '[', '\0', ';', '3', '\0', ';', '4', '\0', 'm', '\0'};

	pattern[2] = txt;
	pattern[5] = fgc;
	pattern[8] = bgc;
	i8250PutStr(pattern);
	
	return;
}

void i8250SetGraphicsModeInformation(void){

	i8250SetGraphicsMode(ANSIESC_GMODE_TXT_ALLATTOFF, ANSIESC_GMODE_FGC_CYAN, ANSIESC_GMODE_BGC_BLACK);
	return;
}

void i8250SetGraphicsModeWarning(void){

	i8250SetGraphicsMode(ANSIESC_GMODE_TXT_BOLD, ANSIESC_GMODE_FGC_YELLOW, ANSIESC_GMODE_BGC_BLACK);
	return;
}

void i8250SetGraphicsModeError(void){

	i8250SetGraphicsMode(ANSIESC_GMODE_TXT_BOLD, ANSIESC_GMODE_FGC_RED, ANSIESC_GMODE_BGC_YELLOW);
	return;
}

void i8250SetGraphicsModePlain(void){

	i8250SetGraphicsMode(ANSIESC_GMODE_TXT_ALLATTOFF, ANSIESC_GMODE_FGC_WHITE, ANSIESC_GMODE_BGC_BLACK);
	return;
}

static void callback i8250InterruptService(void){
	
	reg8b itrpIdent = (IIR & 0x0e) >> 1;

	switch (itrpIdent){
	case 0x01: /*THR empty*/
		i8250DumpCharBufToLine();
		break;
	case 0x02: /*RBR full*/
		i8250InpBuffer[i8250InpBufferProducerPtr] = RBR;
		i8250InpBufferProducerPtr = (i8250InpBufferProducerPtr + 1) % I8250BUFFERSIZE;
		break;
	default:
		break;
	}
	
	return;
}

void i8250Init(){

	/*set baud rate to 115200 bps, 1.8432 MHz crystal used*/
	LCR |= 0x80;
	DLL = 0x01;
	DLM = 0x00;
	LCR &= 0x7f;
	
	/*disable FIFO*/
	FCR &= 0xfe;
	
	/*8 bits data, one stop bit, no parity*/
	LCR = 0x03;
	
	/*disable loopback mode*/
	MCR &= 0xef;
	
	/*enable THR empty and RBR filled interrupt*/
	IER = 0x03;
	
	seti8250ISRCallback(&i8250InterruptService);
	
	return;

}

static uint8 i8250UIntegerToString(uint32 d, uint8 base, uint8 ucase, uint8 minl, unsigned char* str){

	unsigned char* strIndex = NULL;
	unsigned char tempBuffer[64];
	uint8 residual = 0;
	uint32 reminder = d;
	uint8 index = 0;

	if (str == NULL) return 1;
	
	strIndex = &(tempBuffer[0]);
	
	do{ /*convert int to reversed str*/
		residual = reminder % base;
		reminder = (reminder - residual) / base;
		*strIndex = (residual >= 10) ? (residual % 10 + ((ucase == 1) ? (65) : (97))) : (residual + 48);
		++strIndex;
	}while(reminder != 0);
	
	if ((minl != 0) && ((strIndex - &(tempBuffer[0])) < minl)){ /*padding 0 when less than minl*/
		do{
			*strIndex = '0';
			++strIndex;
		}while((strIndex - &(tempBuffer[0])) < minl);
	}
	
	do{ /*correct str order*/
		--strIndex;
		str[index] = *strIndex;
		++index;
	}while(strIndex != &(tempBuffer[0]));
	
	str[index] = '\0'; /*mark for end of string*/
	
	return 0;
}

static uint8 i8250SIntegerToString(sint32 d, uint8 base, uint8 ucase, uint8 minl, unsigned char* str){

	if (str == NULL) return 1;
	
	if (d >= 0){
		i8250UIntegerToString(d, base, ucase, minl, &(str[0]));
	} else {
		str[0] = '-';
		i8250UIntegerToString(~d + 1, base, ucase, minl, &(str[1]));
	}
	
	return 0;
}

void i8250Printf(unsigned char* pattern, ...){

	unsigned char* charIndex = NULL;
	uint8 paramIndex = 0;
	i8250PrintfStateMachine state = i8250PRINTFSTATETEXT;
	unsigned char internalStrBuffer[64];
	uint8 minl = 0;

	if (pattern == NULL) return;

	charIndex = &(pattern[0]);

	do{
		switch(*charIndex){
		case '\0':
			return; /*end of pattern*/
		case '\\': /*escape sequence*/
			state = i8250PRINTFSTATEESCAPE;
			break;
		case '%': /*format specifier*/
			state = i8250PRINTFSTATEFORMAT;
			break;
		default: /*all other characters*/
			switch(state){
			case i8250PRINTFSTATEESCAPE:
				switch(*charIndex){
				case 'n':
					i8250PutChar('\n');
					break;
				case 't':
					i8250PutChar('\t');
					break;
				case '\\':
					i8250PutChar('\\');
					break;
				};
				state = i8250PRINTFSTATETEXT;
				break;
			case i8250PRINTFSTATEFORMAT:
				switch(*charIndex){
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					minl = minl * 10 + ((*charIndex) - 48);
					break;
				case 'd':
					i8250SIntegerToString(GETVARADICPARAMVAL(pattern, paramIndex, sint32), 10, 0, minl, internalStrBuffer);
					++paramIndex;
					i8250PutStr(internalStrBuffer);
					state = i8250PRINTFSTATETEXT;
					minl = 0;
					break;
				case 'u':
					i8250UIntegerToString(GETVARADICPARAMVAL(pattern, paramIndex, uint32), 10, 0, minl, internalStrBuffer);
					++paramIndex;
					i8250PutStr(internalStrBuffer);
					state = i8250PRINTFSTATETEXT;
					minl = 0;
					break;
				case 'x':
					i8250PutChar('0');
					i8250PutChar('x');
					i8250UIntegerToString(GETVARADICPARAMVAL(pattern, paramIndex, uint32), 16, 0, minl, internalStrBuffer);
					++paramIndex;
					i8250PutStr(internalStrBuffer);
					state = i8250PRINTFSTATETEXT;
					minl = 0;
					break;
				case 'X':
					i8250PutChar('0');
					i8250PutChar('x');
					i8250UIntegerToString(GETVARADICPARAMVAL(pattern, paramIndex, uint32), 16, 1, minl, internalStrBuffer);
					++paramIndex;
					i8250PutStr(internalStrBuffer);
					state = i8250PRINTFSTATETEXT;
					minl = 0;
					break;
				case 's':
					i8250PutStr(GETVARADICPARAMPTR(pattern, paramIndex, unsigned char));
					++paramIndex;
					state = i8250PRINTFSTATETEXT;
					minl = 0;
					break;
				};
				break;
			case i8250PRINTFSTATETEXT:
				i8250PutChar(*charIndex);
				break;
			};
		};
		++charIndex;
	}while(1);
	
	return; /*will never execute*/
}

